﻿namespace Blaze.Security
{
    using System;

    /// <summary>
    /// 用于内存加密<see cref="bool"/>型的变量.
    /// </summary>
    public struct SecureBool : IEquatable<SecureBool>
    {
        /// <summary>
        /// 当检测到内存作弊时调用此方法（只会触发一次，之后会被设为null）
        /// </summary>
        public static Action onCheatingDetected;

        private static byte cryptoKey = 198;
        private byte currentCryptoKey;
        private int hiddenValue;
        private bool? fakeValue;
        private bool isInited;

        private SecureBool(int value)
        {
            currentCryptoKey = cryptoKey;
            hiddenValue = value;
            fakeValue = null;
            isInited = true;
        }

        /// <summary>
        /// 设置新的加密密钥。
        /// </summary>
        /// <param name="newKey">新的密钥</param>
        public static void SetNewCryptoKey(byte newKey)
        {
            cryptoKey = newKey;
        }

        /// <summary>
        /// 获取加密后的值。
        /// </summary>
        public int GetEncrypted()
        {
            if (currentCryptoKey != cryptoKey)
            {
                var temp = InternalDecrypt();
                hiddenValue = Encrypt(temp, cryptoKey);
                currentCryptoKey = cryptoKey;
            }

            return hiddenValue;
        }

        /// <summary>
        /// 将变量设置为加密后的值。
        /// </summary>
        /// <param name="encrypted">加密后的值</param>
        public void SetEncrypted(int encrypted)
        {
            hiddenValue = encrypted;
            if (onCheatingDetected != null)
                fakeValue = InternalDecrypt();
        }

        /// <summary>
        /// 使用默认的密钥加密变量。
        /// </summary>
        /// <param name="value">明文</param>
        /// <returns>密文</returns>
        public static int Encrypt(bool value)
        {
            return Encrypt(value, 0);
        }

        /// <summary>
        /// 使用指定的密钥加密变量。
        /// </summary>
        /// <param name="value">明文</param>
        /// <param name="key">密钥</param>
        /// <returns>密文</returns>
        public static int Encrypt(bool value, byte key)
        {
            if (key == 0)
            {
                key = cryptoKey;
            }

            var encryptedValue = value ? 213 : 181;

            encryptedValue ^= key;

            return encryptedValue;
        }

        /// <summary>
        /// 使用默认的密钥解密变量。
        /// </summary>
        /// <param name="value">密文</param>
        /// <returns>明文</returns>
        public static bool Decrypt(int value)
        {
            return Decrypt(value, 0);
        }

        /// <summary>
        /// 使用指定的密钥解密变量。
        /// </summary>
        /// <param name="value">密文</param>
        /// <param name="key">密钥</param>
        /// <returns>明文</returns>
        public static bool Decrypt(int value, byte key)
        {
            if (key == 0)
            {
                key = cryptoKey;
            }

            value ^= key;

            return value != 181;
        }

        public static implicit operator SecureBool(bool value)
        {
            var obscured = new SecureBool(Encrypt(value));

            if (onCheatingDetected != null)
                obscured.fakeValue = value;

            return obscured;
        }

        public static implicit operator bool(SecureBool value)
        {
            return value.InternalDecrypt();
        }

        public override bool Equals(object obj)
        {
            if (!(obj is SecureBool))
                return false;

            var oi = (SecureBool) obj;
            return (hiddenValue == oi.hiddenValue);
        }

        public bool Equals(SecureBool obj)
        {
            return hiddenValue == obj.hiddenValue;
        }

        public override int GetHashCode()
        {
            return InternalDecrypt().GetHashCode();
        }

        public override string ToString()
        {
            return InternalDecrypt().ToString();
        }

        private bool InternalDecrypt()
        {
            if (!isInited)
            {
                currentCryptoKey = cryptoKey;
                hiddenValue = Encrypt(false);
                fakeValue = false;
                isInited = true;
            }

            var key = cryptoKey;

            if (currentCryptoKey != cryptoKey)
            {
                key = currentCryptoKey;
            }

            var value = hiddenValue;
            value ^= key;

            var decrypted = value != 181;

            if (onCheatingDetected != null && fakeValue != null && decrypted != fakeValue)
            {
                onCheatingDetected();
                onCheatingDetected = null;
            }

            return decrypted;
        }
    }
}